package com.mimo.common.configuration.security.rbac.annotation;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.mimo.common.configuration.security.rbac.service.IAccessDenyService;
import com.mimo.common.configuration.security.rbac.service.IAccessIdentityService;
import com.mimo.common.exception.MIMOAuthorizationException;
import com.mimo.common.http.header.HttpHeaderParams;

@Aspect
public class AccessAspect {
  private static final Logger log = LoggerFactory.getLogger(AccessAspect.class);

  /**
   * 排除方法不做访问检测
   */
  private static final Set<Method> EXCLUDE_METHODS = new HashSet<>(Arrays
      .asList(ReflectionUtils.findMethod(BasicErrorController.class, "error", HttpServletRequest.class), ReflectionUtils
          .findMethod(BasicErrorController.class, "errorHtml", HttpServletRequest.class, HttpServletResponse.class)));

  @SuppressWarnings("rawtypes")
  private IAccessDenyService accessService;

  @SuppressWarnings("rawtypes")
  private IAccessIdentityService identityService;

  @SuppressWarnings("rawtypes")
  public AccessAspect(IAccessDenyService accessService, IAccessIdentityService identityService) {
    this.accessService = accessService;
    this.identityService = identityService;
  }

  @Pointcut("within(@org.springframework.stereotype.Controller *)")
  private void controllerAsp() {
    // nothing need to do here.
  }

  @Pointcut("within(@org.springframework.web.bind.annotation.RestController *)")
  private void restControllerAsp() {
    // nothing need to do here.
  }

  @Pointcut("controllerAsp() || restControllerAsp()")
  private void pointcutAtController() {
    // nothing need to do here.
  }

  @SuppressWarnings("unchecked")
  @Before("pointcutAtController()")
  public void doBefore(JoinPoint joinPoint) {
    Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();

    if (!EXCLUDE_METHODS.contains(method)) {// 如果是被排除的方法，则直接返回

      ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
      /**
       * 由于AOP是工作在Controller或者RestController上面,正常情况下, attributes为HTTP请求上下文,必然有值.
       * <p>
       * 然,考虑到部分框架(比如 @link{ZipkinUiAutoConfiguration})往往会在自身成为Controller的同时,又会继承或者实现一些接口,导致在一些公共方法上,没有HTTP上下文
       */
      if (Objects.nonNull(attributes)) {
        AccessFor annotation = AnnotationUtils.findAnnotation(method, AccessFor.class);
        HttpServletRequest request = attributes.getRequest();
        if (Objects.isNull(annotation) && isXAppMark(request)) {
          Object identity = identityService.getIdentity(request);
          try {
            accessService.onDenied(identity, method);
            throw new MIMOAuthorizationException();
          } catch (Exception ex) {
            ReflectionUtils.rethrowRuntimeException(ex);
          }
        }
      } else {
        log.warn("存在拦截到无效HTTP上下文信息:{}", method);
      }

    } // End of Exclude
  }

  private boolean isXAppMark(HttpServletRequest request) {
    return StringUtils.hasText(request.getHeader(HttpHeaderParams.X_Man_For));
  }
}
